Skip to content

S01-23 JavaSE-反射

[TOC]

反射的引入

需求

根据配置文件re.properties指定的类路径和方法名,创建对象并调用方法,无需修改源码(符合开闭原则)。

配置文件re.properties

properties
classfullpath=com.hspedu.Cat
method=hi

传统方式局限

无法通过字符串动态创建对象和调用方法,必须修改源码。

反射解决方案

java
package com.hspedu.reflection.question;

import java.io.FileInputStream;
import java.util.Properties;
import java.lang.reflect.Method;
import java.lang.reflect.Class;

/**
 * 反射引入案例
 */
@SuppressWarnings({"all"})
public class ReflectionQuestion {
    public static void main(String[] args) throws Exception {
        // 1. 读取配置文件
        Properties properties = new Properties();
        properties.load(new FileInputStream("src\\re.properties"));
        String classfullpath = properties.get("classfullpath").toString();
        String methodName = properties.get("method").toString();
        
        // 2. 反射机制创建对象并调用方法
        Class cls = Class.forName(classfullpath); // 加载类
        Object o = cls.newInstance(); // 创建对象
        Method method = cls.getMethod(methodName); // 获取方法对象
        method.invoke(o); // 调用方法
    }
}

反射机制

核心概念

  • 反射机制允许程序在运行时获取类的内部信息(成员变量、构造器、方法等),并操作对象的属性和方法
  • 类加载后,堆中会生成一个Class类型的对象(一个类只有一个Class对象),包含类的完整结构信息,通过该对象可访问类的结构(类似“镜子”)

反射原理示意图

代码阶段(.java文件)→ 编译 → 字节码文件(.class)→ 类加载器(ClassLoader)→ 运行阶段(堆中的Class对象 + 实例对象)
  • Class对象:包含类的成员变量(Field)、构造器(Constructor)、方法(Method)等结构信息
  • 通过Class对象可实现:创建实例、调用方法、操作属性

反射的核心功能

  1. 运行时判断任意对象所属的类
  2. 运行时构造任意类的对象
  3. 运行时获取任意类的成员变量和方法
  4. 运行时调用任意对象的成员变量和方法
  5. 生成动态代理

反射相关主要类

类名功能说明
java.lang.Class代表一个类,包含类的完整结构信息
java.lang.reflect.Method代表类的方法,可调用方法
java.lang.reflect.Field代表类的成员变量,可访问/修改属性
java.lang.reflect.Constructor代表类的构造器,可创建对象

反射的优缺点

  • 优点:动态创建和使用对象,灵活性高,是框架底层核心
  • 缺点:解释执行,执行速度比传统方式慢

反射调用优化

  • 使用setAccessible(true)关闭访问检查,提高反射效率
  • 适用场景:反射调用次数较多时

优化案例

java
package com.hspedu.reflection;

import com.hspedu.Cat;
import java.lang.reflect.Method;

/**
 * 反射性能测试
 */
@SuppressWarnings({"all"})
public class Reflection02 {
    public static void main(String[] args) throws Exception {
        m1(); // 传统方式
        m2(); // 反射方式
        m3(); // 反射优化方式
    }
    
    // 传统方式
    public static void m1() {
        Cat cat = new Cat();
        long start = System.currentTimeMillis();
        for (int i = 0; i < 900000000; i++) {
            cat.hi();
        }
        long end = System.currentTimeMillis();
        System.out.println("m1() 耗时=" + (end - start));
    }
    
    // 反射方式
    public static void m2() throws Exception {
        Class cls = Class.forName("com.hspedu.Cat");
        Object o = cls.newInstance();
        Method hi = cls.getMethod("hi");
        long start = System.currentTimeMillis();
        for (int i = 0; i < 900000000; i++) {
            hi.invoke(o);
        }
        long end = System.currentTimeMillis();
        System.out.println("m2() 耗时=" + (end - start));
    }
    
    // 反射优化(关闭访问检查

Java反射机制

获取Class 类对象(GetClass_.java)

  1. 前提:已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法 forName() 获取,可能抛出 ClassNotFoundException
    • 实例:Class cls1 = Class.forName("java.lang.Cat");
    • 应用场景:多用于配置文件,读取类全路径,加载类。
  2. 前提:若已知具体的类,通过类的 class 属性获取,该方式最为安全可靠,程序性能最高。
    • 实例:Class cls2 = Cat.class;
    • 应用场景:多用于参数传递,比如通过反射得到对应构造器对象。
  3. 前提:已知某个类的实例,调用该实例的 getClass() 方法获取Class对象。
    • 实例:Class clazz = 对象.getClass();(获取运行类型)
    • 应用场景:通过创建好的对象,获取Class对象。
  4. 其他方式(通过类加载器):
    • ClassLoader cl = 对象.getClass().getClassLoader();
    • Class clazz4 = cl.loadClass("类的全类名");
  5. 基本数据类型获取Class对象:
    • 格式:Class cls = 基本数据类型.class
    • 示例:Class<Integer> integerClass = int.class;
  6. 基本数据类型包装类获取Class对象:
    • 格式:Class cls = 包装类.TYPE
    • 示例:Class<Integer> type1 = Integer.TYPE;
java
package com.hspedu.reflection.class_;
import com.hspedu.Car;

/**
 * @author 韩顺平
 * @version 1.0
 * 演示得到Class 对象的各种方式(6)
 */
public class GetClass_ {
    public static void main(String[] args) throws ClassNotFoundException {
        // 1. Class.forName(通过读取配置文件获取全类名)
        String classAllPath = "com.hspedu.Car";
        Class<?> cls1 = Class.forName(classAllPath);
        System.out.println(cls1);

        // 2. 类名.class(应用场景:用于参数传递)
        Class cls2 = Car.class;
        System.out.println(cls2);

        // 3. 对象.getClass()(应用场景:有对象实例)
        Car car = new Car();
        Class cls3 = car.getClass();
        System.out.println(cls3);

        // 4. 通过类加载器获取Class对象
        // (1) 先得到类加载器
        ClassLoader classLoader = car.getClass().getClassLoader();
        // (2) 通过类加载器得到Class 对象
        Class cls4 = classLoader.loadClass(classAllPath);
        System.out.println(cls4);

        // 验证cls1, cls2, cls3, cls4 是同一个对象
        System.out.println(cls1.hashCode());
        System.out.println(cls2.hashCode());
        System.out.println(cls3.hashCode());
        System.out.println(cls4.hashCode());

        // 5. 基本数据类型获取Class对象
        Class<Integer> integerClass = int.class;
        Class<Character> characterClass = char.class;
        Class<Boolean> booleanClass = boolean.class;
        System.out.println(integerClass); // 输出int

        // 6. 包装类通过.TYPE获取Class对象
        Class<Integer> type1 = Integer.TYPE;
        Class<Character> type2 = Character.TYPE;
        System.out.println(type1);
        System.out.println(integerClass.hashCode());
        System.out.println(type1.hashCode());
    }
}

哪些类型有Class 对象

有Class 对象的类型

  1. 外部类、成员内部类、静态内部类、局部内部类、匿名内部类
  2. interface:接口
  3. 数组
  4. enum:枚举
  5. annotation:注解
  6. 基本数据类型
  7. void

应用实例(AllTypeClass.java)

java
package com.hspedu.reflection.class_;
import java.io.Serializable;

/**
 * @author 韩顺平
 * @version 1.0
 * 演示哪些类型有Class 对象
 */
public class AllTypeClass {
    public static void main(String[] args) {
        Class<String> cls1 = String.class; // 外部类
        Class<Serializable> cls2 = Serializable.class; // 接口
        Class<Integer[]> cls3 = Integer[].class; // 数组
        Class<float[][]> cls4 = float[][].class; // 二维数组
        Class<Deprecated> cls5 = Deprecated.class; // 注解
        Class<Thread.State> cls6 = Thread.State.class; // 枚举
        Class<Long> cls7 = long.class; // 基本数据类型
        Class<Void> cls8 = void.class; // void
        Class<Class> cls9 = Class.class; // Class本身

        System.out.println(cls1);
        System.out.println(cls2);
        System.out.println(cls3);
        System.out.println(cls4);
        System.out.println(cls5);
        System.out.println(cls6);
        System.out.println(cls7);
        System.out.println(cls8);
        System.out.println(cls9);
    }
}

类加载

基本说明

  • 反射机制是Java实现动态语言的关键,核心是通过反射实现类动态加载。
  • 静态加载:编译时加载相关类,若不存在则报错,依赖性强。
  • 动态加载:运行时加载需要的类,若不使用则不报错,降低依赖性。

类加载时机

  1. 当创建对象时(new)→ 静态加载
  2. 当子类被加载时,父类也加载 → 静态加载
  3. 调用类中的静态成员时 → 静态加载
  4. 通过反射(Class.forName("com.test.Cat"))→ 动态加载

类加载过程

类加载分为三个阶段:加载(Loading)→ 连接(Linking)→ 初始化(Initialization)

  • 编译:Java源码(.java)→ 字节码文件(.class)
  • 加载后内存布局:方法区存储类的字节码二进制数据,堆区存储类的Class对象

各阶段任务

阶段核心任务
加载(Loading)将类的class文件读入内存,生成代表该类的java.lang.Class对象(由类加载器完成)
连接(Linking)- 验证确保Class文件字节流符合虚拟机要求,无安全风险(文件格式验证、元数据验证等)
连接(Linking)- 准备为静态变量分配内存(方法区),并设置默认初始化值(如0、null、false)
连接(Linking)- 解析将类的二进制数据合并到JRE中
初始化(Initialization)执行<clinit>()方法,收集静态变量赋值动作和静态代码块并合并执行

关键细节

  • 验证阶段可通过-Xverify:none参数关闭大部分验证,缩短加载时间。
  • 准备阶段:静态常量(static final)直接赋值目标值,而非默认值;实例变量不参与。
  • 初始化阶段:<clinit>()方法在多线程环境中会被同步加锁,确保类仅初始化一次。

案例演示(ClassLoad02.java)

java
package com.hspedu.reflection.classload_;

/**
 * @author 韩顺平
 * @version 1.0
 * 说明类加载的链接阶段-准备
 */
public class ClassLoad02 {
    public static void main(String[] args) {
    }
}

class A {
    // 实例属性:准备阶段不分配内存
    public int n1 = 10;
    // 静态变量:准备阶段分配内存,默认初始化0(非20)
    public static int n2 = 20;
    // 静态常量:准备阶段直接赋值30
    public static final int n3 = 30;
}

案例演示(ClassLoad03.java)

java
package com.hspedu.reflection.classload_;

/**
 * @author 韩顺平
 * @version 1.0
 * 演示类加载-初始化阶段
 */
public class ClassLoad03 {
    public static void main(String[] args) throws ClassNotFoundException {
        // 加载B类,触发初始化
        new B();
        // 或调用静态属性触发初始化:System.out.println(B.num);
    }
}

class B {
    static {
        System.out.println("B 静态代码块被执行");
        num = 300;
    }
    static int num = 100;

    public B() {
        System.out.println("B() 构造器被执行");
    }
}

通过反射获取类的结构信息

第一组:java.lang.Class 类方法

方法名功能描述
getName()获取全类名
getSimpleName()获取简单类名
getFields()获取所有public修饰的属性(本类+父类)
getDeclaredFields()获取本类中所有属性
getMethods()获取所有public修饰的方法(本类+父类)
getDeclaredMethods()获取本类中所有方法
getConstructors()获取本类所有public修饰的构造器
getDeclaredConstructors()获取本类中所有构造器
getPackage()以Package形式返回包信息
getSuperClass()以Class形式返回父类信息
getInterfaces()以Class[]形式返回接口信息
getAnnotations()以Annotation[]形式返回注解信息

第二组:java.lang.reflect.Field 类方法

  • getModifiers():以int形式返回修饰符(public=1、private=2、protected=4、static=8、final=16)
  • getName():返回属性名
  • getType():以Class形式返回属性类型

第三组:java.lang.reflect.Method 类方法

  • getModifiers():以int形式返回修饰符(同Field)
  • getReturnType():以Class形式返回返回类型
  • getName():返回方法名
  • getParameterTypes():以Class[]返回参数类型数组

第四组:java.lang.reflect.Constructor 类方法

  • getModifiers():以int形式返回修饰符(同Field)
  • getName():返回构造器全类名
  • getParameterTypes():以Class[]返回参数类型数组

应用实例(ReflectionUtils.java)

java
package com.hspedu.reflection;
import org.junit.jupiter.api.Test;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

/**
 * @author 韩顺平
 * @version 1.0
 * 演示如何通过反射获取类的结构信息
 */
public class ReflectionUtils {
    @Test
    public void api_01() throws ClassNotFoundException {
        // 得到Class 对象
        Class<?> personCls = Class.forName("com.hspedu.reflection.Person");

        // 获取全类名和简单类名
        System.out.println(personCls.getName()); // com.hspedu.reflection.Person
        System.out.println(personCls.getSimpleName()); // Person

        // 获取public修饰的属性(本类+父类)
        Field[] fields = personCls.getFields();
        for (Field field : fields) {
            System.out.println("本类以及父类的public属性=" + field.getName());
        }

        // 获取本类中所有属性
        Field[] declaredFields = personCls.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            System.out.println("本类中所有属性=" + declaredField.getName());
        }

        // 获取public修饰的方法(本类+父类)
        Method[] methods = personCls.getMethods();
        for (Method method : methods) {
            System.out.println("本类以及父类的public方法=" + method.getName());
        }

        // 获取本类中所有方法
        Method[] declaredMethods = personCls.getDeclaredMethods();
        for (Method declaredMethod : declaredMethods) {
            System.out.println("本类中所有方法=" + declaredMethod.getName());
        }

        // 获取public修饰的构造器
        Constructor<?>[] constructors = personCls.getConstructors();
        for (Constructor<?> constructor : constructors) {
            System.out.println("本类的public构造器=" + constructor.getName());
        }

        // 获取本类中所有构造器
        Constructor<?>[] declaredConstructors = personCls.getDeclaredConstructors();
        for (Constructor<?> declaredConstructor : declaredConstructors) {
            System.out.println("本类中所有构造器=" + declaredConstructor.getName());
        }

        // 获取包信息、父类信息、接口信息、注解信息
        System.out.println("包信息=" + personCls.getPackage());
        Class<?> superclass = personCls.getSuperclass();
        System.out.println("父类的class对象=" + superclass);
        Class<?>[] interfaces = personCls.getInterfaces();
        for (Class<?> anInterface : interfaces) {
            System.out.println("接口信息=" + anInterface);
        }
        Annotation[] annotations = personCls.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println("注解信息=" + annotation);
        }
    }

    @Test
    public void api_02() throws ClassNotFoundException {
        Class<?> personCls = Class.forName("com.hspedu.reflection.Person");

        // 打印本类所有属性的详细信息
        Field[] declaredFields = personCls.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            System.out.println("属性名=" + declaredField.getName()
                    + " 修饰符值=" + declaredField.getModifiers()
                    + " 类型=" + declaredField.getType());
        }

        // 打印本类所有方法的详细信息
        Method[] declaredMethods = personCls.getDeclaredMethods();
        for (Method declaredMethod : declaredMethods) {
            System.out.println("方法名=" + declaredMethod.getName()
                    + " 访问修饰符值=" + declaredMethod.getModifiers()
                    + " 返回类型=" + declaredMethod.getReturnType());
            // 打印方法形参类型
            Class<?>[] parameterTypes = declaredMethod.getParameterTypes();
            for (Class<?> parameterType : parameterTypes) {
                System.out.println("方法形参类型=" + parameterType);
            }
        }

        // 打印本类所有构造器的详细信息
        Constructor<?>[] declaredConstructors = personCls.getDeclaredConstructors();
        for (Constructor<?> declaredConstructor : declaredConstructors) {
            System.out.println("====================");
            System.out.println("构造器名=" + declaredConstructor.getName());
            // 打印构造器形参类型
            Class<?>[] parameterTypes = declaredConstructor.getParameterTypes();
            for (Class<?> parameterType : parameterTypes) {
                System.out.println("构造器形参类型=" + parameterType);
            }
        }
    }
}

// 父类A
class A {
    public String hobby;
    public void hi() {}
    public A() {}
    public A(String name) {}
}

// 接口IA、IB
interface IA {}
@Deprecated
interface IB {}

// 测试类Person
class Person extends A implements IA, IB {
    // 属性
    public String name;
    protected static int age; // 修饰符值:4+8=12
    String job;
    private double sal;

    // 构造器
    public Person() {}
    public Person(String name) {}
    private Person(String name, int age) {}

    // 方法
    public void m1(String name, int age, double sal) {}
    protected String m2() { return null; }
    void m3() {}
    private void m4() {}
}

通过反射创建对象

核心方法

类别方法名功能描述
Class类newInstance()调用public无参构造器,获取对象
Class类getConstructor(Class...clazz)根据参数列表,获取public构造器对象
Class类getDeclaredConstructor(Class...clazz)根据参数列表,获取所有构造器对象
Constructor类setAccessible(true)暴破:允许访问private构造器/方法/属性
Constructor类newInstance(Object...obj)调用构造器,传入实参创建对象

案例演示(ReflecCreateInstance.java)

java
package com.hspedu.reflection;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/**
 * @author 韩顺平
 * @version 1.0
 * 演示通过反射机制创建实例
 */
public class ReflecCreateInstance {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException,
            InstantiationException, NoSuchMethodException, InvocationTargetException {
        // 1. 获取User类的Class对象
        Class<?> userClass = Class.forName("com.hspedu.reflection.User");

        // 2. 通过public无参构造器创建实例
        Object o1 = userClass.newInstance();
        System.out.println(o1);

        // 3. 通过public有参构造器创建实例(User(String name))
        Constructor<?> constructor = userClass.getConstructor(String.class);
        Object o2 = constructor.newInstance("hsp");
        System.out.println("o2=" + o2);

        // 4. 通过private有参构造器创建实例(User(int age, String name))
        Constructor<?> constructor1 = userClass.getDeclaredConstructor(int.class, String.class);
        // 暴破:允许访问private构造器
        constructor1.setAccessible(true);
        Object o3 = constructor1.newInstance(100, "张三丰");
        System.out.println("o3=" + o3);
    }
}

// 测试类User
class User {
    private int age = 10;
    private String name = "韩顺平教育";

    // public无参构造器
    public User() {}

    // public有参构造器
    public User(String name) {
        this.name = name;
    }

    // private有参构造器
    private User(int age, String name) {
        this.age = age;
        this.name = name;
    }

    @Override
    public String toString() {
        return "User [age=" + age + ", name=" + name + "]";
    }
}

通过反射访问类中的成员

访问属性(ReflecAccessProperty.java)

核心步骤
  1. 根据属性名获取Field对象:Field f = clazz.getDeclaredField(属性名);
  2. 暴破:f.setAccessible(true);(允许访问private属性)
  3. 访问属性:
    • 设置值:f.set(o, 值);(o为对象,静态属性可传null)
    • 获取值:f.get(o);
案例演示
java
package com.hspedu.reflection;
import java.lang.reflect.Field;

/**
 * @author 韩顺平
 * @version 1.0
 * 演示反射操作属性
 */
public class ReflecAccessProperty {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException,
            InstantiationException, NoSuchFieldException {
        // 1. 获取Student类的Class对象
        Class<?> stuClass = Class.forName("com.hspedu.reflection.Student");

        // 2. 创建对象
        Object o = stuClass.newInstance();
        System.out.println(o.getClass()); // 运行类型:Student

        // 3. 操作public属性age
        Field ageField = stuClass.getField("age");
        ageField.set(o, 88); // 设置age值为88
        System.out.println(o); // 输出Student [age=88, name=null]
        System.out.println(ageField.get(o)); // 输出88

        // 4. 操作private static属性name
        Field nameField = stuClass.getDeclaredField("name");
        nameField.setAccessible(true); // 暴破
        nameField.set(null, "老韩~"); // 静态属性,o可传null
        System.out.println(o); // 输出Student [age=88, name=老韩~]
        System.out.println(nameField.get(null)); // 输出老韩~
    }
}

// 测试类Student
class Student {
    public int age;
    private static String name;

    public Student() {}

    @Override
    public String toString() {
        return "Student [age=" + age + ", name=" + name + "]";
    }
}

访问方法(ReflecAccessMethod.java)

核心步骤
  1. 根据方法名和参数列表获取Method对象:Method m = clazz.getDeclaredMethod(方法名, 参数类型.class);
  2. 暴破:m.setAccessible(true);(允许访问private方法)
  3. 调用方法:Object returnValue = m.invoke(o, 实参列表);(静态方法o可传null)
案例演示
java
package com.hspedu.reflection;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * @author 韩顺平
 * @version 1.0
 * 演示通过反射调用方法
 */
public class ReflecAccessMethod {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException,
            IllegalAccessException, InstantiationException, InvocationTargetException {
        // 1. 获取Boss类的Class对象
        Class<?> bossCls = Class.forName("com.hspedu.reflection.Boss");

        // 2. 创建对象
        Object o = bossCls.newInstance();

        // 3. 调用public方法hi(String s)
        Method hiMethod = bossCls.getDeclaredMethod("hi", String.class);
        hiMethod.invoke(o, "韩顺平教育~"); // 输出:hi 韩顺平教育~

        // 4. 调用private static方法say(int, String, char)
        Method sayMethod = bossCls.getDeclaredMethod("say", int.class, String.class, char.class);
        sayMethod.setAccessible(true); // 暴破
        // 静态方法可传null调用
        Object result1 = sayMethod.invoke(null, 100, "张三", '男');
        System.out.println(result1); // 输出:100 张三 男
        Object result2 = sayMethod.invoke(o, 200, "李四", '女');
        System.out.println(result2); // 输出:200 李四 女

        // 5. 调用返回对象的方法m1()
        Method m1Method = bossCls.getDeclaredMethod("m1");
        Object result3 = m1Method.invoke(o);
        System.out.println("返回对象的运行类型=" + result3.getClass()); // 输出:class com.hspedu.reflection.Monster
    }
}

class Monster {}

class Boss {
    public int age;
    private static String name;

    // public方法
    public void hi(String s) {
        System.out.println("hi " + s);
    }

    // private static方法
    private static String say(int n, String s, char c) {
        return n + " " + s + " " + c;
    }

    // 返回Monster对象的方法
    public Monster m1() {
        return new Monster();
    }
}

本章作业

练习1:通过反射修改私有成员变量

java
// Homework01.java
class PrivateTest {
    private String name = "hellokitty";
    public String getName() {
        return name;
    }
}

// 要求:利用Class类得到私有name属性,修改其值,并调用getName()打印

练习2:利用反射和File创建文件

java
// Homework02.java
// 要求:
// 1. 利用Class类的forName方法得到File类的class对象
// 2. 在控制台打印File类的所有构造器
// 3. 通过newInstance方法创建File对象,并创建E:\mynew.txt文件
// 提示:正常创建文件:File file = new File("d:\\aa.txt"); file.createNewFile();